Date-time range
canonStart and end with time, all-day toggle and duration presets.
M4c (owner 28 Sep 2026, approved): all-day renders the canonical DateRangeField (from–to in one field, shared calendar, day count) instead of two DateFields with their own day arithmetic — one shape for a range of days; `.dp-field.range` ends share the field width (width 0 + flex 1), so the term row no longer overflows on 390 px (acrm branch feat/celodenni-rozsah). acrm !4087 (28 Sep 2026, owner): the visible duration next to times uses `fmtDurShort` — „45 m“ · „1 h“ · „1 h 30 m“ (in the context of time „m“ = minutes). Screen readers keep the full „min“ via `fmtDur` (TimeField option aria-label, DateTimeRange hidden live text) so „m“ is not read as metres. Other durations in the app (timeline, Setkání, tasks, reminders) keep „min“.
Used in 3 files · 3 areas · 3× — components/calendar, components/meetings, pages/ZasedackyPage
@acrm/ui <DateTimeRangeField>.dp-stack .dp-termin .dp-timegroup .dp-switches +3SourceStoryLegacy card dates.htmlDefault
export const Default = () => {
const [value, setValue] = useTerm(timed);
return (
<div style={grid}>
<DateTimeRangeField value={value} onChange={setValue} today={TODAY} />
</div>
);
};Variants
export const Variants = () => {
const [meeting, setMeeting] = useTerm(timed);
const [allDay, setAllDay] = useTerm({
date: '2026-08-26',
from: null,
to: null,
allDay: true,
toDate: '2026-08-26',
});
const [multi, setMulti] = useTerm({
date: '2026-08-26',
from: null,
to: null,
allDay: true,
toDate: '2026-08-28',
});
const [overnight, setOvernight] = useTerm({
date: '2026-08-26',
from: 22 * 60,
to: 2 * 60,
allDay: false,
toDate: '2026-08-27',
});
return (
<div style={row}>
<Labeled label="timed — date, from – to, duration">
<DateTimeRangeField value={meeting} onChange={setMeeting} today={TODAY} />
</Labeled>
<Labeled label="all day — one date, no times">
<DateTimeRangeField value={allDay} onChange={setAllDay} today={TODAY} />
</Labeled>
<Labeled label="all day, multi-day — end date + day count">
<DateTimeRangeField value={multi} onChange={setMulti} today={TODAY} />
</Labeled>
<Labeled label="overnight (allowOvernight) — end before start rolls to the next day">
<DateTimeRangeField
value={overnight}
onChange={setOvernight}
allowOvernight
today={TODAY}
/>
</Labeled>
</div>
);
};States
export const States = () => {
const [free, setFree] = useTerm(timed);
const [busy, setBusy] = useTerm({ ...timed, from: 14 * 60, to: 15 * 60 });
const [noOvernight, setNoOvernight] = useTerm({
date: '2026-08-26',
from: 22 * 60,
to: 2 * 60,
allDay: false,
toDate: '2026-08-27',
});
const [room, setRoom] = useTerm(timed);
const [empty, setEmpty] = useTerm({ date: null, from: null, to: null, allDay: false });
return (
<div style={row}>
<Labeled label="availability — free (.dp-status.ok)">
<DateTimeRangeField
value={free}
onChange={setFree}
today={TODAY}
availability={() => ({ free: true, text: 'Všichni mají volno' })}
/>
</Labeled>
<Labeled label="availability — busy (.dp-status.busy)">
<DateTimeRangeField
value={busy}
onChange={setBusy}
today={TODAY}
availability={() => ({ free: false, text: 'Jana Nováková má schůzku' })}
/>
</Labeled>
<Labeled label="overnight not allowed — end before start is an error">
<DateTimeRangeField value={noOvernight} onChange={setNoOvernight} today={TODAY} />
</Labeled>
<Labeled label="allowAllDay=false — meeting room, no toggle">
<DateTimeRangeField value={room} onChange={setRoom} allowAllDay={false} today={TODAY} />
</Labeled>
<Labeled label="empty — no date, no times">
<DateTimeRangeField value={empty} onChange={setEmpty} today={TODAY} />
</Labeled>
</div>
);
};Composition
export const Composition = () => {
const [value, setValue] = useTerm({ ...timed, to: 9 * 60 + 45 });
return (
<div style={grid}>
<Labeled label="meeting dialog — Termín field, footer">
<div style={{ display: 'grid', gap: 'var(--sp-4)', minWidth: 480 }}>
<Field label="Termín">
<DateTimeRangeField
value={value}
onChange={setValue}
today={TODAY}
availability={() => ({ free: true, text: 'Zasedačka Praha je volná' })}
/>
</Field>
<div
style={{
display: 'flex',
gap: 'var(--sp-3)',
justifyContent: 'flex-end',
paddingTop: 'var(--sp-4)',
borderTop: '1px solid var(--border)',
}}
>
<Button kbd="Esc">Cancel</Button>
<Button variant="primary" kbd="⌘↵">
Save
</Button>
</div>
</div>
</Labeled>
</div>
);
};